package de.mmis.core.base.abstracttree.serializer.selector;

import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.Map;
import java.util.Set;
import java.util.TreeSet;

import de.mmis.core.base.abstracttree.InnerNode;
import de.mmis.core.base.abstracttree.serializer.DeserializableException;
import de.mmis.core.base.abstracttree.serializer.SerializerHelper;
import de.mmis.core.base.gclass.GArray;
import de.mmis.core.base.gclass.GClass;
import de.mmis.core.base.gclass.GParameterized;

/**
 * {@link InstanceSelector} representing an abstract list type. The target type
 * determines the type of the list ({@link ArrayList}s, {@link LinkedList}s,
 * {@link Set}s...)
 * 
 * @author Christian Koch
 */
public class ListSelector extends InstanceSelector {

	private final InnerNode struct;
	private final ClassLoader classLoader;

	public ListSelector(InnerNode struct, ClassLoader classLoader, String id,
			Map<String, Object> idmap) {
		super(id, idmap);

		this.struct = struct;
		this.classLoader = classLoader;
	}

	@SuppressWarnings({ "unchecked", "rawtypes" })
	@Override
	public Object createInstance(GClass gclass)
			throws InstanceSelectorException {

		if (gclass instanceof GArray)
			throw new InstanceSelectorException("cannot convert " + this.struct
					+ " to type " + gclass + ": type is no iterable");

		Class<?> base = gclass.getBaseClass();

		if (gclass.getBaseClass() == Object.class)
			return createInstance(new GParameterized(Map.class, new GClass[] {
					GClass.fromClass(Object.class),
					GClass.fromClass(Object.class) }));

		if (!Iterable.class.isAssignableFrom(base))
			throw new InstanceSelectorException("cannot convert " + this.struct
					+ " to type " + gclass + ": type is no iterable");

		if (base.isInterface() || Modifier.isAbstract(base.getModifiers())) {
			if (base.isAssignableFrom(ArrayList.class))
				return createInstance(new GParameterized(ArrayList.class,
						GClass.getGenericsOf(gclass, Iterable.class)));

			if (base.isAssignableFrom(LinkedList.class))
				return createInstance(new GParameterized(LinkedList.class,
						GClass.getGenericsOf(gclass, Iterable.class)));

			if (base.isAssignableFrom(HashSet.class))
				return createInstance(new GParameterized(HashSet.class,
						GClass.getGenericsOf(gclass, Iterable.class)));

			if (base.isAssignableFrom(TreeSet.class))
				return createInstance(new GParameterized(TreeSet.class,
						GClass.getGenericsOf(gclass, Iterable.class)));

			throw new InstanceSelectorException(
					"iterable type \""
							+ gclass
							+ "\" is either abstract or an interface and there is no known method of instantiating a subclass of it");
		}

		GClass comptype;
		GClass[] comptypes = GClass.getGenericsOf(gclass, Collection.class);
		if (comptypes == null || comptypes.length != 1)
			comptype = GClass.fromClass(Object.class);
		else
			comptype = comptypes[0];

		Collection ret;
		try {
			ret = (Collection<?>) base.newInstance();
		} catch (InstantiationException e) {
			throw new InstanceSelectorException(
					"cannot instantiate Collection of type \"" + gclass + "\"",
					e);
		} catch (IllegalAccessException e) {
			throw new InstanceSelectorException(
					"cannot instantiate Collection of type \"" + gclass + "\"",
					e);
		}

		setID(ret);

		for (int i = 0; i < this.struct.getNumberOfSubTrees(); i++) {
			Object o;
			try {
				o = SerializerHelper.deserializeObject(
						this.struct.getSubTree(i), comptype, this.classLoader,
						getIDMap());
			} catch (DeserializableException e) {
				throw new InstanceSelectorException(
						"deserialization of collection element " + i
								+ " failed", e);
			}
			ret.add(o);
		}
		return ret;
	}
}
